/*
 * MainWindow.java
 *
 * Copyright 2011 Emilio Simoes 
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Created on 3 de Dez de 2011 by emilio
 */
package pt.isec.gps.fastnote;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Desktop.Action;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JTabbedPane;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Utilities;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.HTMLEditorKit.LinkController;

/**
 * Main window for the application.
 * 
 * @author Emilio Simoes
 * @version $Revision$
 */
public class MainWindow extends JFrame {

    private static final long serialVersionUID = 7459509290445216094L;

    private static ResourceBundle resources = ResourceBundle.getBundle("pt.isec.gps.fastnote.resources.Strings");

    private JButton newButton;

    private JButton boldButton;

    private JButton italicButton;

    private JButton underlineButton;

    private JButton colorButton;

    private JButton bulletsButton;

    private JButton removeButton;

    private JComboBox headingsComboBox;

    private JTabbedPane tabbedPane;

    private final Integer[] HEADINGS = { 1, 2, 3, 4, 5 };

    private Map<String, JTextPane> editors;

    private Map<JTextPane, HTMLEditorKit> editorsKit;

    private FileManager manager = new FileManager();

    private boolean save = true;

    private Thread thread;

    public MainWindow() {
        super(resources.getString("window.title"));

        editors = new HashMap<String, JTextPane>();

        editorsKit = new HashMap<JTextPane, HTMLEditorKit>();

        createComponents();
        layoutComponents();
        registerEvents();

        setIconImage(getImage("fast_note.png"));
        setFocusable(true);
        setPreferredSize(new Dimension(640, 480));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        validate();
        pack();
        setLocationByPlatform(true);

        Map<String, Note> notes = manager.restoreAllNotes();
        for (Note note : notes.values()) {
            createNote(note);
        }

        if (tabbedPane.getTabCount() == 0) {
            createNote(null);
        }

        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (save) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                    }
                    saveNotes();
                }
            }
        });
        thread.start();
    }

    /**
     * Registers the components event listeners.
     */
    private void registerEvents() {
        newButton.addActionListener(new NewNoteListener());
        removeButton.addActionListener(new RemoveNoteListener());
        boldButton.addActionListener(new BoldListener());
        italicButton.addActionListener(new ItalicListener());
        underlineButton.addActionListener(new UnderlineListener());
        colorButton.addActionListener(new ColorListener());
        bulletsButton.addActionListener(new BulletsListener());
        headingsComboBox.addActionListener(new HeadingListener());
        addWindowListener(new MainWindowListener());
        addKeyListener(new WindowsKeyListener());
    }

    /**
     * Disposes the components in the frame.
     */
    private void layoutComponents() {
        JToolBar toolBar = new JToolBar("Main Toolbar");
        toolBar.add(newButton);
        toolBar.addSeparator();
        toolBar.addSeparator();
        toolBar.add(headingsComboBox);
        toolBar.addSeparator();
        toolBar.add(boldButton);
        toolBar.add(italicButton);
        toolBar.add(underlineButton);
        toolBar.add(colorButton);
        toolBar.addSeparator();
        toolBar.add(bulletsButton);
        toolBar.addSeparator();
        toolBar.add(removeButton);

        getContentPane().add(toolBar, BorderLayout.PAGE_START);
        getContentPane().add(tabbedPane, BorderLayout.CENTER);
    }

    /**
     * Creates the window components.
     */
    private void createComponents() {
        newButton = new JButton(getIcon("new.png"));
        newButton.setToolTipText(resources.getString("window.new"));
        newButton.setMnemonic(KeyEvent.VK_N);
        boldButton = new JButton(getIcon("bold.png"));
        boldButton.setToolTipText(resources.getString("window.bold"));
        boldButton.setMnemonic(KeyEvent.VK_B);
        italicButton = new JButton(getIcon("italic.png"));
        italicButton.setToolTipText(resources.getString("window.italic"));
        italicButton.setMnemonic(KeyEvent.VK_I);
        underlineButton = new JButton(getIcon("underline.png"));
        underlineButton.setToolTipText(resources.getString("window.underline"));
        underlineButton.setMnemonic(KeyEvent.VK_U);
        colorButton = new JButton(getIcon("color.png"));
        colorButton.setToolTipText(resources.getString("window.color"));
        colorButton.setMnemonic(KeyEvent.VK_R);
        bulletsButton = new JButton(getIcon("bullets.png"));
        bulletsButton.setToolTipText(resources.getString("window.bullets"));
        bulletsButton.setMnemonic(KeyEvent.VK_L);
        removeButton = new JButton(getIcon("remove.png"));
        removeButton.setToolTipText(resources.getString("window.remove"));
        removeButton.setMnemonic(KeyEvent.VK_R);
        removeButton.setEnabled(false);

        headingsComboBox = new JComboBox(HEADINGS);
        headingsComboBox.setSelectedIndex(2);
        ComboBoxRenderer renderer = new ComboBoxRenderer();
        headingsComboBox.setRenderer(renderer);
        headingsComboBox.setMaximumSize(new Dimension(200, 100));

        tabbedPane = new JTabbedPane();
    }

    private void browse(String address) {
        Desktop desktop = Desktop.getDesktop();
        if (desktop.isSupported(Action.BROWSE)) {
            try {
                desktop.browse(new URI(address));
            } catch (IOException e) {
            } catch (URISyntaxException e) {
            }
        }
    }

    /**
     * Gets a string from the resources file.
     * 
     * @param key The key of the resource.
     * @return The string for the provided key.
     */
    private ImageIcon getIcon(String key) {
        String name = "/pt/isec/gps/fastnote/resources/" + key;
        return new ImageIcon(getClass().getResource(name));
    }

    /**
     * Gets a resource image.
     * 
     * @param key The key of the resource.
     * @return An ImageIcon for the provided key.
     */
    private BufferedImage getImage(String key) {
        ImageIcon icon = getIcon(key);
        BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(),
                BufferedImage.TYPE_4BYTE_ABGR);
        Graphics g = image.createGraphics();
        icon.paintIcon(null, g, 0, 0);
        return image;
    }

    private void createNote(Note note) {
        HTMLEditorKit editorKit = new HTMLEditorKit();
        HTMLDocument documentHTML = (HTMLDocument) editorKit.createDefaultDocument();
        JTextPane editor = new JTextPane(documentHTML);
        editor.setContentType("text/html");
        initListener(editor);
        MyLinkController handler = new MyLinkController();
        editor.addMouseListener(handler);
        editor.addHyperlinkListener(new HTMLListener());
        String fileName = manager.generateFileName();
        if (note != null) {
            editor.setText(note.getContent());
            fileName = note.getFileName();
        }
        editors.put(fileName, editor);
        editorsKit.put(editor, editorKit);
        tabbedPane.addTab(resources.getString("window.note"), getIcon("edit.png"), editor);
        tabbedPane.setSelectedComponent(editor);
        removeButton.setEnabled(true);
    }

    private void removeNote() {
        int selectedIndex = tabbedPane.getSelectedIndex();
        if (selectedIndex >= 0) {
            JTextPane editor = (JTextPane) tabbedPane.getSelectedComponent();
            tabbedPane.remove(selectedIndex);
            String fileName = "";
            for (Map.Entry<String, JTextPane> pair : editors.entrySet()) {
                if (pair.getValue() == editor) {
                    manager.removeNote(pair.getKey());
                    fileName = pair.getKey();
                    break;
                }
            }
            editorsKit.remove(editor);
            editors.remove(fileName);
            if (tabbedPane.getTabCount() == 0) {
                createNote(null);
            }
        }
    }

    private void saveNotes() {
        Map<String, Note> notes = new HashMap<String, Note>();
        for (Map.Entry<String, JTextPane> pair : editors.entrySet()) {
            Note note = new Note(pair.getKey(), pair.getValue().getText());
            notes.put(note.getFileName(), note);
        }
        manager.save(notes);
    }

    /**
     * Renders the combo box items.
     */
    class ComboBoxRenderer extends JLabel implements ListCellRenderer {
        private static final long serialVersionUID = 0xABCDEF0000000008L;

        ComboBoxRenderer() {
            setOpaque(true);
            setHorizontalAlignment(LEFT);
            setVerticalAlignment(CENTER);
        }

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
                boolean cellHasFocus) {
            int selectedIndex = ((Integer) value).intValue();
            if (isSelected) {
                setBackground(list.getSelectionBackground());
                setForeground(list.getSelectionForeground());
            } else {
                setBackground(list.getBackground());
                setForeground(list.getForeground());
            }
            ImageIcon icon = MainWindow.this.getIcon(MessageFormat.format("heading_{0}.png", selectedIndex));
            if (icon != null) {
                setIcon(icon);
            }
            setText(resources.getString(MessageFormat.format("window.heading{0}", selectedIndex)));
            setFont(list.getFont());
            return this;
        }
    }

    /**
     * New note click
     */
    class NewNoteListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            createNote(null);
            tabbedPane.getSelectedComponent().requestFocusInWindow();
        }
    }

    /**
     * Remove note click
     */
    class RemoveNoteListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            removeNote();
            tabbedPane.getSelectedComponent().requestFocusInWindow();
        }
    }

    /**
     * Bold text click
     */
    class BoldListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            JTextPane tmpPanel = (JTextPane) tabbedPane.getSelectedComponent();
            HTMLEditorKit tmpEditorKit = editorsKit.get(tmpPanel);
            new TextEffectsBold().formatTextEffects(tabbedPane, tmpEditorKit, e);
            tabbedPane.getSelectedComponent().requestFocusInWindow();
        }
    }

    /**
     * Italic text click
     */
    class ItalicListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            JTextPane tmpPanel = (JTextPane) tabbedPane.getSelectedComponent();
            HTMLEditorKit tmpEditorKit = editorsKit.get(tmpPanel);
            new TextEffectsItalic().formatTextEffects(tabbedPane, tmpEditorKit, e);
            tabbedPane.getSelectedComponent().requestFocusInWindow();
        }
    }

    /**
     * Underline text click
     */
    class UnderlineListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            JTextPane tmpPanel = (JTextPane) tabbedPane.getSelectedComponent();
            HTMLEditorKit tmpEditorKit = editorsKit.get(tmpPanel);
            new TextEffectsUnderline().formatTextEffects(tabbedPane, tmpEditorKit, e);
            tabbedPane.getSelectedComponent().requestFocusInWindow();
        }
    }

    /**
     * Background color click
     */
    class ColorListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            JTextPane tmpPanel = (JTextPane) tabbedPane.getSelectedComponent();
            HTMLEditorKit tmpEditorKit = editorsKit.get(tmpPanel);
            new TextEffectsColor().formatTextEffects(tabbedPane, tmpEditorKit, e);
            tabbedPane.getSelectedComponent().requestFocusInWindow();
        }
    }

    /**
     * Bulleted text click
     */
    class BulletsListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            JTextPane tmpPanel = (JTextPane) tabbedPane.getSelectedComponent();
            HTMLEditorKit tmpEditorKit = editorsKit.get(tmpPanel);
            new TextEffectsBullet().formatTextEffects(tabbedPane, tmpEditorKit, e);
            tabbedPane.getSelectedComponent().requestFocusInWindow();
        }
    }

    /**
     * Change text size click
     */
    class HeadingListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            int selected = headingsComboBox.getSelectedIndex();
            JTextPane tmpPanel = (JTextPane) tabbedPane.getSelectedComponent();
            HTMLEditorKit tmpEditorKit = editorsKit.get(tmpPanel);
            switch (selected) {
                case 0:
                    new TextEffectsHeadings1().formatTextEffects(tabbedPane, tmpEditorKit, e);
                    break;
                case 1:
                    new TextEffectsHeadings2().formatTextEffects(tabbedPane, tmpEditorKit, e);
                    break;
                case 2:
                    new TextEffectsHeadings3().formatTextEffects(tabbedPane, tmpEditorKit, e);
                    break;
                case 3:
                    new TextEffectsHeadings4().formatTextEffects(tabbedPane, tmpEditorKit, e);
                    break;
                case 4:
                    new TextEffectsHeadings5().formatTextEffects(tabbedPane, tmpEditorKit, e);
                    break;
            }
            tabbedPane.getSelectedComponent().requestFocusInWindow();
        }
    }

    class WindowsKeyListener implements KeyListener {

        /*
         * (non-Javadoc)
         * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
         */
        @Override
        public void keyTyped(KeyEvent e) {
        }

        /*
         * (non-Javadoc)
         * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
         */
        @Override
        public void keyPressed(KeyEvent e) {
            if (e.isControlDown()) {
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_B:
                        boldButton.doClick();
                        break;
                    case KeyEvent.VK_I:
                        italicButton.doClick();
                        break;
                    case KeyEvent.VK_U:
                        underlineButton.doClick();
                        break;
                    case KeyEvent.VK_H:
                        colorButton.doClick();
                        break;
                    case KeyEvent.VK_L:
                        bulletsButton.doClick();
                        break;
                    case KeyEvent.VK_1:
                        headingsComboBox.setSelectedIndex(0);
                        break;
                    case KeyEvent.VK_2:
                        headingsComboBox.setSelectedIndex(1);
                        break;
                    case KeyEvent.VK_3:
                        headingsComboBox.setSelectedIndex(2);
                        break;
                    case KeyEvent.VK_4:
                        headingsComboBox.setSelectedIndex(3);
                        break;
                    case KeyEvent.VK_5:
                        headingsComboBox.setSelectedIndex(4);
                        break;
                    case KeyEvent.VK_N:
                        createNote(null);
                        break;
                }
            } else {
                if (e.getKeyCode() == KeyEvent.VK_DELETE) {
                    removeNote();
                }
            }
        }

        /*
         * (non-Javadoc)
         * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
         */
        @Override
        public void keyReleased(KeyEvent e) {
        }
    }

    class MainWindowListener extends WindowAdapter {
        /*
         * (non-Javadoc)
         * @see java.awt.event.WindowAdapter#windowClosing(java.awt.event.WindowEvent)
         */
        @Override
        public void windowClosing(WindowEvent e) {
            save = false;
            thread.interrupt();
            try {
                thread.join();
            } catch (InterruptedException e1) {
            }
            saveNotes();
            super.windowClosing(e);
        }
    }

    boolean isInProgress = false;

    private void initListener(final JTextPane editor) {
        editor.getDocument().addDocumentListener(new DocumentListener() {
            public void insertUpdate(DocumentEvent event) {
                final DocumentEvent e = event;
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        if (e.getDocument() instanceof HTMLDocument && e.getOffset() > 0 && e.getLength() == 1
                                && !isInProgress) {
                            try {

                                HTMLDocument doc = (HTMLDocument) e.getDocument();
                                String text = doc.getText(e.getOffset(), e.getLength());
                                if (text.charAt(0) == ' ' || text.charAt(0) == '\n' || text.charAt(0) == '\t') {
                                    int start = Utilities.getWordStart(editor, e.getOffset() - 1);
                                    text = doc.getText(start, e.getOffset() - start);
                                    if (text.startsWith("www")) {
                                        isInProgress = true;
                                        // the next 3 lines are necessary to create separate text elem
                                        // to be replaced with link
                                        SimpleAttributeSet a = new SimpleAttributeSet();
                                        a.addAttribute("DUMMY_ATTRIBUTE_NAME", "DUMMY_ATTRIBUTE_VALUE");
                                        doc.setCharacterAttributes(start, text.length(), a, false);

                                        Element elem = doc.getCharacterElement(start);
                                        String html = "<a href='http://" + text + "'>" + text + "</a>";
                                        doc.setOuterHTML(elem, html);
                                        isInProgress = false;
                                    }
                                }
                            } catch (BadLocationException e1) {
                                e1.printStackTrace();
                            } catch (IOException e1) {
                                e1.printStackTrace();
                            }
                        }
                    }
                });
            }

            public void removeUpdate(DocumentEvent e) {
            }

            public void changedUpdate(DocumentEvent e) {
            }
        });
    }

    class MyLinkController extends LinkController {

        private static final long serialVersionUID = 7002457187450255130L;

        public void mouseClicked(MouseEvent e) {
            JEditorPane editor = (JEditorPane) e.getSource();

            if (editor.isEditable() && SwingUtilities.isLeftMouseButton(e)) {
                if (e.getClickCount() == 2) {
                    editor.setEditable(false);
                    super.mouseClicked(e);
                    editor.setEditable(true);
                }
            }
        }
    }

    class HTMLListener implements HyperlinkListener {
        public void hyperlinkUpdate(HyperlinkEvent e) {
            if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                browse(e.getURL().toString());
            }
        }
    }
}
